using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Autofac;
using Autofac.Extras.DynamicProxy;
using Blog.Common.DB;
using Blog.Common.Helper;
using Blog.Common.Redis;
using Blog.Core3.AuthHelper.OverWite;
using Blog.Core3.Extensions; 
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json.Serialization;
using SqlSugar;
using Swashbuckle.AspNetCore.Filters;
namespace Blog.Core3
{ 
    public class Startup
    { 
        public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
        } 
        public string ApiName { get; set; } = "Blog.Core";
        public string basePath { get; set; } = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;
        public IConfiguration Configuration { get; }  
        public void ConfigureServices(IServiceCollection services)
        {
            //接口返回格式 修改
            services.AddControllers()
            .AddNewtonsoftJson(options =>
                               options.SerializerSettings.ContractResolver = new DefaultContractResolver()
                               );

            //数据库配置,初始化
            //BaseDBConfig.ConnectionString = Configuration.GetSection("AppSettings:SqlServerConnection").Value;

            services.AddSingleton<IRedisCacheManager, RedisCacheManager>();
            services.AddSqlsugarSetup();
            services.AddAutoMapperSetup();
            #region Swagger 配置
            services.AddSwaggerGen(c =>
            {
                #region 文档基本信息配置
                c.SwaggerDoc("V1", new OpenApiInfo
                {
                  
                    Version = "V1",
                    Title = $"{ApiName} 接口文档——Netcore 3.0",
                    Description = $"{ApiName} HTTP API V1",
                    Contact = new OpenApiContact { Name = ApiName, Email = "Cgrain0206@gmail.com", Url = new Uri("https://www.jianshu.com/u/94102b59cc2a") },
                    License = new OpenApiLicense { Name = ApiName, Url = new Uri("https://www.jianshu.com/u/94102b59cc2a") }
                });
                c.OrderActionsBy(o => o.RelativePath);
                #endregion

                #region  读取 XML  

                //就是这里！！！！！！！！！
                var xmlPath = Path.Combine(basePath, "Blog.Core3.xml");//这个就是刚刚配置的xml文件名
                c.IncludeXmlComments(xmlPath, true);//默认的第二个参数是false，这个是controller的注释，记得修改

                var xmlModelPath = Path.Combine(basePath, "Blog.Core3.Model.xml");//这个就是Model层的xml文件名
                c.IncludeXmlComments(xmlModelPath);

                #endregion

                #region 配置权限
                c.OperationFilter<AddResponseHeadersFilter>();
                c.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
                c.OperationFilter<SecurityRequirementsOperationFilter>();
                #endregion
                #region Token绑定到ConfigureServices
                c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
                {
                    Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}（注意两者之间是一个空格）\"",
                    Name = "Authorization",//jwt默认的参数名称
                    In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
                    Type = SecuritySchemeType.ApiKey
                });

                #endregion

            });
          
            #region 授权 权限认证
            // 1【授权】、这个和上边的异曲同工，好处就是不用在controller中，写多个 roles 。
            // 然后这么写 [Authorize(Policy = "Admin")]
            services.AddAuthorization(options =>
            {
                options.AddPolicy("Client", policy => policy.RequireRole("Client").Build()); 
                options.AddPolicy("Admin", policy => policy.RequireRole("Admin").Build());  //只能这个权限访问
                options.AddPolicy("SystemOrAdmin", policy => policy.RequireRole("Admin", "System")); //这两个权限都可以访问
            });
            #endregion

            #region 【第二步：配置认证服务】
            // 令牌验证参数
            //读取配置文件
            var audienceConfig = Configuration.GetSection("Audience");
            var symmetricKeyAsBase64 = AppSecretConfig.Audience_Secret_String;
            var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
            var signingKey = new SymmetricSecurityKey(keyByteArray);
            var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = signingKey,
                ValidateIssuer = true,
                ValidIssuer = audienceConfig["Issuer"],//发行人
                ValidateAudience = true,
                ValidAudience = audienceConfig["Audience"],//订阅人
                ValidateLifetime = true,
                ClockSkew = TimeSpan.FromSeconds(30),
                RequireExpirationTime = true,
            };
            #endregion
            //2.1【认证】、core自带官方JWT认证
            #region 开启Bearer认证
            services.AddAuthentication
                (o => {
                    o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                    o.DefaultChallengeScheme = nameof(ApiResponseHandler);
                    o.DefaultForbidScheme = nameof(ApiResponseHandler);
                })
               //("Bearer")
               // 添加JwtBearer服务
               .AddJwtBearer(o =>
             {
                 o.TokenValidationParameters = tokenValidationParameters; 
                 o.Events = new JwtBearerEvents
                 {
                     OnAuthenticationFailed = context =>
                     {
                         // 如果过期，则把<是否过期>添加到，返回头信息中
                         if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                         {
                             context.Response.Headers.Add("Token-Expired", "true");
                         }
                        
                         return Task.CompletedTask;
                     }
                 };
             })
               .AddScheme<AuthenticationSchemeOptions, ApiResponseHandler>(nameof(ApiResponseHandler), o => { });
            #endregion

            #endregion
 
            #region  跨域配置  
            services.AddCors(c =>
            { 
                // 配置策略
                c.AddPolicy("LimitRequests", policy =>
                {
                    // 支持多个域名端口，端口号后不要带/斜杆：比如localhost:8000/ 
                    // http://127.0.0.1:1818 和 http://localhost:1818 不同的协议,所以不一样
                    policy
                    .WithOrigins("http://127.0.0.1:1818", "http://localhost:8080", "http://localhost:8021", "http://localhost:8081", "http://localhost:1818")
                    .AllowAnyHeader()//允许任意头
                    .AllowAnyMethod();//允许任意方法
                });
            });
            #endregion

        }

        /// <summary>
        /// 依赖注入方法  Net Core 3.1 需要这么做
        /// </summary>
        /// <param name="builder"></param>
        public void ConfigureContainer(ContainerBuilder builder)
        { 
            //注册要通过反射创建的组件
            // builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>();
            // builder.RegisterType<BlogCacheAOP>();//可以直接替换其他拦截器
            // builder.RegisterType<BlogRedisCacheAOP>();//可以直接替换其他拦截器
            // builder.RegisterType<BlogLogAOP>();//这样可以注入第二个  

            #region 带有接口层的服务注入

            #region Service.dll 注入，有对应接口
             try
            {
                var servicesDllFile = Path.Combine(basePath, "Blog.Core3.Services.dll");
                var assemblysServices = Assembly.LoadFrom(servicesDllFile);


                //设置APO切面
                var cacheType = new List<Type>();
                if (Appsettings.app(new string[] { "AppSettings", "RedisCachingAOP", "Enabled" }).ObjToBool())
                {
                    // cacheType.Add(typeof(BlogRedisCacheAOP));
                }
                if (Appsettings.app(new string[] { "AppSettings", "MemoryCachingAOP", "Enabled" }).ObjToBool())
                {
                   // cacheType.Add(typeof(BlogCacheAOP));
                }
                if (Appsettings.app(new string[] { "AppSettings", "LogAOP", "Enabled" }).ObjToBool())
                {
                  //  cacheType.Add(typeof(BlogLogAOP));
                }

                builder.RegisterAssemblyTypes(assemblysServices)
                          .AsImplementedInterfaces()
                          .InstancePerLifetimeScope()
                          .EnableInterfaceInterceptors()
                        //引用Autofac.Extras.DynamicProxy;
                        // 如果你想注入两个，就这么写  InterceptedBy(typeof(BlogCacheAOP), typeof(BlogLogAOP));
                        // 如果想使用Redis缓存，请必须开启 redis 服务，端口号我的是6319，如果不一样还是无效，否则请使用memory缓存 BlogCacheAOP
                          .InterceptedBy(cacheType.ToArray());//允许将拦截器服务的列表分配给注册。 
                #endregion

                #region Repository.dll 注入，有对应接口
                var repositoryDllFile = Path.Combine(basePath, "Blog.Core3.Repository.dll");
                var assemblysRepository = Assembly.LoadFrom(repositoryDllFile);
                builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces();
                #endregion

            }
            catch (Exception ex)
            {
                //  " + ex.Message + "\n" + ex.InnerException
                throw new Exception($" 我们解耦了,需要重新生成一些,错误原因:{ex.Message}");
            }

            #endregion
             
            #region 没有接口层的服务层注入

            ////因为没有接口层，所以不能实现解耦，只能用 Load 方法。
            ////注意如果使用没有接口的服务，并想对其使用 AOP 拦截，就必须设置为虚方法
            ////var assemblysServicesNoInterfaces = Assembly.Load("Blog.Core.Services");
            ////builder.RegisterAssemblyTypes(assemblysServicesNoInterfaces);

            #endregion

            #region 没有接口的单独类 class 注入
            //////只能注入该类中的虚方法
            //builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love)))
            //    .EnableClassInterceptors()
            //    .InterceptedBy(typeof(BlogLogAOP));

            #endregion
             
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            #region Swagger
            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint($"/swagger/V1/swagger.json", $"{ApiName} V1");
                // 看微软官方文档配置
                c.RoutePrefix = string.Empty;
            });
            #endregion
            app.UseRouting();
            app.UseCors();//添加 Cors 跨域中间件
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}
